use std::any::{Any, TypeId};
use std::sync::Arc;

use thiserror::Error;

use craftdig_math::*;

use crate::block::{BlkId, BlockStateSnapshot};
use crate::world::{msg_relay, WorldView};

#[derive(Debug, Error)]
pub enum BehaviorError {
    #[error("unsupported message kind")]
    UnsupportedMessageKind,

    #[error("unimplemented")]
    Unimplemented,
}

pub trait Message: Clone + Sync + Send + Any {}

/// Impl of logic to perform on receiving a block.
pub trait MsgAccept {
    type Msg: Message;
    fn accept(&self, inst: &mut BlockContext, msg: &Self::Msg) -> Result<(), BehaviorError>;
}

/// Uniquely refers to a given type of message that can be sent to a block.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct MsgTyId(u16);

impl From<u16> for MsgTyId {
    fn from(value: u16) -> Self {
        Self(value)
    }
}

pub struct MsgTyReg {
    // TODO
}

impl MsgTyReg {
    #[allow(unused)]
    pub fn new<M: Message>() -> Self {
        // TODO
        Self {}
    }
}

/// Wrapper around a raw message that we unwrap in the appropriate handlers.
#[derive(Clone)]
pub struct MessageContainer {
    msg_ty_id: MsgTyId,
    msg: Arc<dyn Any>,
}

impl MessageContainer {
    /// Wraps a message in a container, providing a registry-given message type ID.
    pub fn new<M: Message>(msg_ty_id: MsgTyId, msg: M) -> Self {
        Self {
            msg_ty_id,
            msg: Arc::new(msg) as Arc<dyn Any>,
        }
    }

    pub fn ty(&self) -> MsgTyId {
        self.msg_ty_id
    }
}

/// Wrapper type around message handler impl function.
pub struct MsgHandlerImpl {
    msg_type_id: TypeId,
    handler:
        Box<dyn Fn(&mut BlockContext, MessageContainer) -> Result<(), BehaviorError> + Sync + Send>,
}

impl MsgHandlerImpl {
    pub fn new<M: Message, A: MsgAccept<Msg = M> + Sync + Send + 'static>(acc: A) -> Self {
        make_handler_wrapper_shim(acc)
    }

    /// Checks if the handler impl can accept the given message.
    pub fn can_accept_msg(&self, mc: MessageContainer) -> bool {
        let msg_tid = mc.msg.type_id();
        msg_tid == self.msg_type_id
    }

    /// Forwards the message and instance context to the handler impl, or
    /// returns an error if the message type doesn't match.
    pub fn try_accept(
        &self,
        inst: &mut BlockContext,
        msg: MessageContainer,
    ) -> Result<(), BehaviorError> {
        (self.handler)(inst, msg)
    }
}

/// Function that takes an arbitrary behavior message handler and converts it to
/// a wrapper impl that does the type checking.
fn make_handler_wrapper_shim<M: Message, A: MsgAccept<Msg = M> + Sync + Send + 'static>(
    acc: A,
) -> MsgHandlerImpl {
    let h = Box::new(move |bi: &mut BlockContext, mc: MessageContainer| {
        // Ideally, this check would never fail.  We would just ignore messages
        // sent to blocks that don't support them as part of the normal message
        // routing logic before they get here.
        let m = mc
            .msg
            .downcast_ref::<M>()
            .ok_or_else(|| BehaviorError::UnsupportedMessageKind)?;
        acc.accept(bi, m)
    });

    MsgHandlerImpl {
        msg_type_id: TypeId::of::<M>(),
        handler: h,
    }
}

/// Data passed to a behavior acceptor about the instance of the block in a world.
pub struct BlockContext<'w> {
    pos: BlkCoord,
    world_view: &'w dyn WorldView,
    blk_changes: Vec<msg_relay::BlockChange>,
}

impl<'w> BlockContext<'w> {
    pub fn new(pos: BlkCoord, world_view: &'w dyn WorldView) -> Self {
        Self {
            pos,
            world_view,
            blk_changes: Vec::new(),
        }
    }

    /// The block's position in the world.
    pub fn pos(&self) -> BlkCoord {
        self.pos
    }

    /// Gets the ID of the block we're looking at.
    pub fn get_self_blkid(&self) -> BlkId {
        self.world_view
            .get_block_id(self.pos())
            .expect("blockinst: block missing from world")
    }

    /// Gets the position of a neighboring block in some direction.
    pub fn get_neighbor_pos(&self, dir: Direction) -> BlkCoord {
        BlkCoord::from(*self.pos().inner() + dir.to_unit_vec())
    }

    /// Gets the ID of a neighboring block in some direction.
    pub fn get_neighbor_blkid(&self, dir: Direction) -> Option<BlkId> {
        self.world_view.get_block_id(self.get_neighbor_pos(dir))
    }

    /// Submits an action sourced from this block to be applied soon.
    pub fn submit_block_change(&mut self, target: BlkCoord, state: BlockStateSnapshot) {
        self.blk_changes
            .push(msg_relay::BlockChange::new(target, state));
    }

    /// Returns the internal state that may have been built up and returns it.
    pub fn unpack(self) -> Vec<msg_relay::BlockChange> {
        self.blk_changes
    }
}
